package cn.adolf.carmaster;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.cgutman.adblib.AdbCrypto;

import java.util.concurrent.atomic.AtomicBoolean;

import cn.adolf.carmaster.cgutman.AdbUtils;
import cn.adolf.carmaster.cgutman.console.CommandHistory;
import cn.adolf.carmaster.cgutman.console.ConsoleBuffer;
import cn.adolf.carmaster.cgutman.devconn.DeviceConnection;
import cn.adolf.carmaster.cgutman.devconn.DeviceConnectionListener;
import cn.adolf.carmaster.cgutman.service.ShellService;
import cn.adolf.carmaster.cgutman.ui.Dialog;
import cn.adolf.carmaster.cgutman.ui.SpinnerDialog;
import cn.adolf.carmaster.helper.ShareManager;
import cn.adolf.carmaster.ui.AboutFragment;
import cn.adolf.carmaster.ui.HelpFragment;
import cn.adolf.carmaster.ui.MenuActivity;
import cn.adolf.carmaster.ui.OperationFragment;
import cn.adolf.carmaster.ui.SimpleFragment;

public class CommandActivity extends AppCompatActivity implements DeviceConnectionListener, View.OnKeyListener {
    private static final String TAG = CommandActivity.class.getSimpleName();
    public static final String EXTRA_IP = "IP";
    public static final String EXTRA_PORT = "Port";


    private String ip;
    private int port;

    private DeviceConnection connection;

    private Intent service;
    private ShellService.ShellServiceBinder binder;

    private SpinnerDialog connectWaiting;

    private final static String PREFS_FILE = "AdbCmdHistoryPrefs";
    private static final int MAX_COMMAND_HISTORY = 15;
    private CommandHistory commandHistory;

    private boolean updateGui;
    private AtomicBoolean updateQueued = new AtomicBoolean();
    private AtomicBoolean updateRequired = new AtomicBoolean();

    private boolean autoScrollEnabled = true;
    private boolean userScrolling = false;
    private boolean scrollViewAtBottom = true;

    private ConsoleBuffer lastConsoleBuffer;

    private StringBuilder commandBuffer = new StringBuilder();


    private ServiceConnection serviceConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName arg0, IBinder arg1) {
            binder = (ShellService.ShellServiceBinder) arg1;
            if (connection != null) {
                binder.removeListener(connection, CommandActivity.this);
            }
            connection = CommandActivity.this.connectOrLookupConnection(ip, port);
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            binder = null;
        }
    };
    private TextView shellView;
    private ScrollView shellScroller;
    private Button btnGetPkg;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_command);

        initView();

        /* Pull previous command history (if any) */
        commandHistory = CommandHistory.loadCommandHistoryFromPrefs(MAX_COMMAND_HISTORY, this, PREFS_FILE);

        service = new Intent(this, ShellService.class);
        getApplicationContext().startService(service);

        onNewIntent(getIntent());
    }

    private void initView() {
        shellView = (TextView) findViewById(R.id.tvCmd);
        shellScroller = (ScrollView) findViewById(R.id.svCmd);
        // OperationFragment operationFragment = (OperationFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_content);
        SimpleFragment operationFragment = (SimpleFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_content);
        operationFragment.setCmdListener(cmd -> {
            Log.d(TAG, "onCmdClick: " + cmd);
            doCommand(cmd);

        });

        // FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        // ft.replace(R.id.fragment_content, operationFragment);
        // ft.commit();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.my_memu, menu);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_help:
                Intent help = new Intent(this, MenuActivity.class);
                help.putExtra(MenuActivity.EXTRA_TYPE, MenuActivity.MENU_HELP);
                startActivity(help);
                break;
            case R.id.menu_about:
                Intent about = new Intent(this, MenuActivity.class);
                about.putExtra(MenuActivity.EXTRA_TYPE, MenuActivity.MENU_ABOUT);
                startActivity(about);
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onNewIntent(Intent shellIntent) {
        super.onNewIntent(shellIntent);
        ip = shellIntent.getStringExtra("IP");
        port = shellIntent.getIntExtra("Port", -1);
        if (ip == null || port == -1) {
            finish();
            return;
        }

        setTitle("星瑞 - " + ip + ":" + port);

        if (binder == null) {
            /* Bind the service if we're not bound already. After binding, the callback will
             * perform the initial connection. */
            getApplicationContext().bindService(service, serviceConn, Service.BIND_AUTO_CREATE);
        } else {
            /* We're already bound, so do the connect or lookup immediately */
            if (connection != null) {
                binder.removeListener(connection, this);
            }
            connection = connectOrLookupConnection(ip, port);
        }
    }

    private DeviceConnection startConnection(String host, int port) {
        /* Display the connection progress spinner */
        connectWaiting = SpinnerDialog.displayDialog(this, "正在连接 " + ip + ":" + port,
                "请确保你的设备打开了wifi adb开关.\n\n" +
                        "点击车机的弹框确定连接", true);

        /* Create the connection object */
        DeviceConnection conn = binder.createConnection(host, port);

        /* Add this activity as a connection listener */
        binder.addListener(conn, this);

        /* Begin the async connection process */
        conn.startConnect();

        return conn;
    }

    private DeviceConnection connectOrLookupConnection(String host, int port) {
        DeviceConnection conn = binder.findConnection(host, port);
        if (conn == null) {
            /* No existing connection, so start the connection process */
            conn = startConnection(host, port);
        } else {
            /* Add ourselves as a new listener of this connection */
            binder.addListener(conn, this);
        }
        return conn;
    }

    private void doCommand(String cmd) {
        /* Append the command to our command buffer (which is empty) */
        commandBuffer.append(cmd);

        /* Add the command to the previous command list */
        commandHistory.add(cmd);

        /* Append a newline since it's not included in the command itself */
        commandBuffer.append('\n');

        /* Send it to the device */
        connection.queueCommand(commandBuffer.toString());

        /* Clear the textbox and command buffer */
        commandBuffer.setLength(0);

        /* Force scroll to the bottom */
        scrollViewAtBottom = true;
        doAsyncGuiUpdate();
    }


    @Override
    protected void onDestroy() {
        /* Save the command history first */
        commandHistory.save();

        if (binder != null && connection != null) {
            /* Tell the service about our impending doom */
            binder.notifyDestroyingActivity(connection);

            /* Dissociate our activity's listener */
            binder.removeListener(connection, this);
        }

        /* If the connection hasn't actually finished yet,
         * close it before terminating */
        if (connectWaiting != null) {
            AdbUtils.safeClose(connection);
        }

        /* Unbind from the service since we're going away */
        if (service != null) {
            getApplicationContext().unbindService(serviceConn);
        }

        Dialog.closeDialogs();
        SpinnerDialog.closeDialogs();
        super.onDestroy();
    }

    @Override
    public void onResume() {
        /* Tell the service about our UI state change */
        if (binder != null) {
            binder.notifyResumingActivity(connection);
        }

        /* There might be changes we need to display */
        updateTerminalView();

        /* Start updating the GUI again */
        updateGui = true;
        super.onResume();
    }

    @Override
    public void onPause() {
        /* Tell the service about our UI state change */
        if (binder != null) {
            binder.notifyPausingActivity(connection);
        }

        /* Stop updating the GUI for now */
        updateGui = false;
        super.onPause();
    }


    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        // if (keyCode == KeyEvent.KEYCODE_ENTER) {
        //     /* Just call the onEditorAction function to handle this for us */
        //     return onEditorAction((TextView)v, EditorInfo.IME_ACTION_DONE, event);
        // }
        // else {
        return false;
        // }
    }

    private void updateTerminalView() {
        if (lastConsoleBuffer != null) {
            lastConsoleBuffer.updateTextView(shellView);
        }

        if (autoScrollEnabled) {
            shellView.post(() -> {
                if (scrollViewAtBottom) {
                    shellScroller.smoothScrollTo(0, shellView.getBottom());
                }
            });
        }
    }

    @Override
    public void notifyConnectionEstablished(DeviceConnection devConn) {
        Log.d(TAG, "notifyConnectionEstablished: ");
        connectWaiting.dismiss();
        connectWaiting = null;
        ShareManager.getInstance().saveIp(ip);
        ShareManager.getInstance().savePort(String.valueOf(port));

    }

    @Override
    public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
        Log.d(TAG, "notifyConnectionFailed: " + e.getMessage());
        connectWaiting.dismiss();
        connectWaiting = null;

        Dialog.displayDialog(this, "连接失败", e.getMessage(), true);
    }

    @Override
    public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
        Log.d(TAG, "notifyStreamFailed: " + e.getMessage());
        Dialog.displayDialog(this, "连接已终止", e.getMessage(), true);
    }

    @Override
    public void notifyStreamClosed(DeviceConnection devConn) {
        Log.d(TAG, "notifyStreamClosed: ");
        Dialog.displayDialog(this, "连接已关闭", "The connection was gracefully closed.", true);
    }

    @Override
    public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
        Log.d(TAG, "loadAdbCrypto: ");
        return AdbUtils.readCryptoConfig(getFilesDir());
    }

    @Override
    public boolean canReceiveData() {
        /* We just handle console updates */
        return false;
    }

    @Override
    public void receivedData(DeviceConnection devConn, byte[] data, int offset,
                             int length) {
    }

    @Override
    public boolean isConsole() {
        return true;
    }

    private void setGuiDirty() {
        /* Remember that a GUI update is needed */
        updateRequired.set(true);
    }

    private void doAsyncGuiUpdate() {
        /* If no update is required, do nothing */
        if (!updateRequired.get()) {
            return;
        }

        /* If an update isn't already queued, fire one off */
        if (updateQueued.compareAndSet(false, true)) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    /* Wait for a few milliseconds to avoid spamming GUI updates */

                    try {
                        Thread.sleep(250);
                    } catch (InterruptedException e) {
                        return;
                    }

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            /* We won't need an update again after this */
                            updateRequired.set(false);

                            /* Redraw the terminal */
                            updateTerminalView();

                            /* This update is finished */
                            updateQueued.set(false);

                            /* If someone updated the console between the time that we
                             * started redrawing and when we finished, we need to update
                             * the GUI again, otherwise the GUI update could be missed. */
                            if (updateRequired.get()) {
                                doAsyncGuiUpdate();
                            }
                        }
                    });
                }
            }).start();
        }
    }

    @Override
    public void consoleUpdated(DeviceConnection devConn, ConsoleBuffer console) {
        Log.d(TAG, "consoleUpdated: " + console);
        lastConsoleBuffer = console;

        setGuiDirty();
        if (updateGui && !userScrolling && scrollViewAtBottom) {
            doAsyncGuiUpdate();
        }
    }


    public interface OnCmdListener {
        void onCmd(String cmd);
    }


}